// -*- C++ -*-
#include <pthread.h>
#include <errno.h>

#ifndef __SEMAPHORE_H__
#define __SEMAPHORE_H__

#ifndef ETIMEDOUT   /* Connection timed out */
#define ETIMEDOUT       EIO
#endif

// 複数の待ちを開放できるセマフォ
// Sequencerとかで複数の待っている人達に対して開放を指示
// postは全員開放去れたかどうかかきにしない
// post_waitは全員開放されるまでまつ
struct MonitorSem
{
    MonitorSem() :value(0), waiting(0) 
        {
            pthread_mutex_init(&mutex,NULL);
            pthread_cond_init(&cond, NULL);
        }
    ~MonitorSem() {post_wait();}
    int wait(unsigned long timeout = 0)
        {
            // waitingの操作自体はmutexの中
            int ret = 0;
            pthread_mutex_lock(&mutex);
            waiting++;
            if (timeout == 0) 
            {
                while(value == 0) pthread_cond_wait(&cond, &mutex);
            }
            else
            {
                struct timeval nowt;
                struct timespec stopt;
                // 現在時間取得
                gettimeofday(&nowt, NULL);
                // timeout時間を設定
                stopt.tv_sec = nowt.tv_sec + timeout / 1000;
                stopt.tv_nsec = (nowt.tv_usec + (timeout % 1000) * 1000) * 1000;
                while(value == 0) 
                    if (pthread_cond_timedwait(&cond, &mutex, &stopt) == ETIMEDOUT) {
                        std::cerr << "timeout" << std::endl;
                        ret = -ETIMEDOUT;
                        break;
                    }
            }
            waiting--;
            pthread_mutex_unlock(&mutex);
            return ret;
        }
    void post()
        {
            if (value == 0 || waiting != 0) {
                pthread_mutex_lock(&mutex);
                value=1;
                pthread_cond_broadcast(&cond);
                pthread_mutex_unlock(&mutex);
            }
        }
    void post_wait()
        {
            post();
            while(waiting>0) usleep(0);
        }
    void lock()
        {
            pthread_mutex_lock(&mutex);
            value=0;
            pthread_cond_broadcast(&cond);
            pthread_mutex_unlock(&mutex);
        }
private:
    // コピー、代入の禁止
    MonitorSem(const MonitorSem &m);
    MonitorSem& operator=(const MonitorSem &m);
    int value;
    int waiting;
    pthread_cond_t  cond;
    pthread_mutex_t mutex;
};

// 途中で強制的に待ちを止められるセマフォ
// 普通のセマフォだとsem_postでしか待ちを止められない
// sem_postは成功通知しかできない
// (デバイスが死んだときに、デバイス待ちのセマフォを強制的にエラー値をかえしてとめたい)
struct InterruptibleSem
{
    InterruptibleSem(int v) : value(v)
        {
            pthread_mutex_init(&mutex,NULL);
            pthread_cond_init(&cond, NULL);
        }
    // 指定された値にする
    void set(int v)
        {
            pthread_mutex_lock(&mutex);
            value = v;
            pthread_cond_broadcast(&cond);
            pthread_mutex_unlock(&mutex);
        }
    int get()
        {
            return value;
        }
    // 指定された値の間待つ
    int wait(unsigned long timeout = 0)
        {
            pthread_mutex_lock(&mutex);
            if (timeout == 0) 
            {
                while(value == 0) pthread_cond_wait(&cond, &mutex);
            }
            else
            {
                struct timeval nowt;
                struct timespec stopt;
                // 現在時間取得
                gettimeofday(&nowt, NULL);
                // timeout時間を設定
                stopt.tv_sec = nowt.tv_sec + timeout / 1000;
                stopt.tv_nsec = (nowt.tv_usec + (timeout % 1000) * 1000) * 1000;
                while(value == 0) 
                    if (pthread_cond_timedwait(&cond, &mutex, &stopt) == ETIMEDOUT) {
                        std::cerr << "timeout" << std::endl;
                        value = -ETIMEDOUT;
                        break;
                    }
            }
            if (value > 0) value--;
            pthread_mutex_unlock(&mutex);
            return value;
        }

    void post()
        {
            pthread_mutex_lock(&mutex);
            value++;
            pthread_cond_broadcast(&cond);
            pthread_mutex_unlock(&mutex);
        }
private:
    // コピー、代入の禁止
    InterruptibleSem(const InterruptibleSem &m);
    InterruptibleSem& operator=(const InterruptibleSem &m);
    int value;
    pthread_cond_t  cond;
    pthread_mutex_t mutex;
};

#endif

